/*
 * Copyright (c) [2005] [Jeffrey Moore]
 *
 * Redistributions in source code form must reproduce the above copyright and 
 * this condition.
 *
 * The contents of this file are subject to the Sun Project JXTA License 
 * Version 1.1 (the "License"); you may not use this file except in compliance 
 * with the License. A copy of the License is available at 
 * http://www.jxta.org/jxta_license.html.
 *
 */

/*
 * RemoteMonitorControl.java
 *
 * Created on April 9, 2005, 10:06 AM
 */

package net.jxta.myjxta.plugins.vijxta;

import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.MessageElement;
import net.jxta.logging.Logging;
import net.jxta.myjxta.dialog.DialogListener;
import net.jxta.myjxta.dialog.DialogMessage;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class represents a video monitor of a remote video data feed.  The remote
 * data are images passed via DialogMessage's. This calss also acts as a DialogListener
 * for all incoming messages.  Video messages are parsed here while Command
 * messages are send to CallControl.
 *
 * @author jamoore
 */
public final class RemoteMonitorControl extends Thread implements DialogListener, Monitor {

    private static final Logger LOG = Logger.getLogger(RemoteMonitorControl.class.getName());

    private ViJxtaCallControl viJxtaCallControl = null;

    private LinkedList incomingBuffer = null;

    private int monitorState = STOPPED;

    private int receiveState = STOPPED;

    private MyLock bufferStarvedLock = null;

    private String originator = null;

    private int imageCompression = 0;

    private int imagesPerMessage = 0;

    private long timeOfLastMessage = 0;

    private long lastReceivedMessage = 0;

    private int messagesReceived = 0;

    private int bytesReceived = 0;

    private int imageSize = 0;

    private int averageImageSize = 0;

    private final long averageImageDecodeTime = 0;

    private long imageDecodeTime = 0;

    private Dimension formatSize = null;

    private String formatType = null;

    private RemoteMonitorCanvas monitorComponent = null;

    private boolean receive = false;


    private long imageDrawTime = 0;

    private final long averageImageDrawTime = 0;

    private MyLock pausedLock = null;

    /**
     * Creates a new instance of RemoteMonitorControl
     */
    public RemoteMonitorControl(ViJxtaCallControl viJxtaCallControl) {

        LOG.setLevel(Level.INFO);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("RemoteMonitorControl Instantiated");
        }

        this.viJxtaCallControl = viJxtaCallControl;

        getCallControl().getDialog().addListener(this);

        incomingBuffer = new LinkedList();

        bufferStarvedLock = new MyLock();

        pausedLock = new MyLock();

    }

    public void run() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Receive Thread : RUN");
        }

        while (true) {

            try {

                if (getReceiveState() == this.STOPPED) {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("run : Stopped");
                    }
                    //we should send any remaining data in buffer first
                    break;
                }

                if (getReceiveState() == this.PAUSED) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("run : Paused");
                    }

                    synchronized (pausedLock) {

                        pausedLock.wait();
                    }
                }
                if (incomingBuffer.size() > 0) {

                    displayImage();

                } else {
                    synchronized (bufferStarvedLock) {

                        bufferStarvedLock.setLocked(true);

                        bufferStarvedLock.wait();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Receive Thread: Ended");
        }
    }


    public void receive(DialogMessage msg) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info ("Begin receive (DialogMessage)");
            //LOG.info("ReceiveDialogMessage");
        }

        lastReceivedMessage = System.currentTimeMillis();

        /** retrieve the messages command */
        String sessionCommand = getMessageSessionCommand(msg);


        if (sessionCommand.equals(ViJxtaCallControl.COMMAND_VIJXTA_DATA)) {


            if (this.isReceive() && getCallControl().getProtocolState() == ViJxtaCallControl.SESSION_VIJXTA_INCALL) {

                receiveViJxtaData(msg);
            }

        } else {

            if (getOriginator() == null) {

                setOriginator(msg.getOriginator());
            }
            /** otherwise it is a session command that we need to deal with */
            getCallControl().callControl(sessionCommand, msg);

        }
    }

    private String getMessageSessionCommand(DialogMessage msg) {

        String command = null;


        MessageElement me = msg.getMessageElement(ViJxtaCallControl.TAG_SESSION_COMMAND);

        if (me != null) {
            command = me.toString();
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("sessionMessageCommandElement is null");
            }
        }

        return command;
    }


    public void receiveViJxtaData(DialogMessage msg) {

        this.timeOfLastMessage = System.currentTimeMillis();

        byte[] imageBytes = getImageBytes(msg);


        if (imageBytes != null) {


            incomingBuffer.addLast(new ByteArrayInputStream(imageBytes));


            this.messagesReceived += 1;

            this.bytesReceived += imageBytes.length;

            boolean locked = bufferStarvedLock.isLocked();

            if (locked && incomingBuffer.size() > 0) {

                synchronized (bufferStarvedLock) {

                    bufferStarvedLock.setLocked(false);

                    bufferStarvedLock.notifyAll();
                }
            }

        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("reveiceViJxtaData : image data is null");
            }
        }

    }

    private byte[] getImageBytes(DialogMessage msg) {

        byte[] imageBytes = null;

        MessageElement me = msg.getMessageElement(ViJxtaCallControl.TAG_IMAGE_DATA);

        if (me != null) {


            ByteArrayMessageElement imageDataElelment =
                    (ByteArrayMessageElement) me;

            imageBytes = me.getBytes(true);


        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("imageMessageElement is null");
            }
        }

        this.imageSize = imageBytes.length;

        if (averageImageSize != 0) {

            this.averageImageSize = (averageImageSize + imageBytes.length) / 2;
        } else {

            this.averageImageSize = imageBytes.length;
        }


        return imageBytes;
    }

    public void startMonitor() {

        this.setMonitorState(STARTED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("startMonitor");
        }

    }


    public void startReceive() {

        this.setReceiveState(STARTED);

        this.start();

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("startReceive");
        }
    }

    public void stopReceive() {

        this.setReceiveState(STOPPED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("stopMonitor");
        }
    }

    public void pauseReceive() {

        this.setReceiveState(PAUSED);
    }

    public void resumeReceive() {

        this.setReceiveState(STARTED);

        synchronized (pausedLock) {

            pausedLock.notifyAll();

        }
    }

    public void obtainHardware() {

        /** no hardware to obtain so we just setup the monitor */
        if (this.getFormatSize() != null) {

            monitorComponent = new RemoteMonitorCanvas(this.getFormatSize());
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("Cannot instantiate RemoteMonitor - Format size is NULL.");
            }
        }

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("obtainHardware");
        }
    }

    public void releaseHardware() {
        /** no real hardware to release*/
        if (monitorComponent != null) {

            monitorComponent = null;
        }

        if (incomingBuffer != null) {

            incomingBuffer.clear();

            incomingBuffer = null;
        }

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("releaseMonitor");
        }
    }


    public void stopMonitor() {

        this.setMonitorState(this.STOPPED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("stopMonitor");
        }
    }


    protected void displayImage() {


        getMonitorComponent().newImage((ByteArrayInputStream) incomingBuffer.getFirst());

        incomingBuffer.removeFirst();


    }

    public void pauseMonitor() {

        this.setMonitorState(this.PAUSED);
    }

    public void resumeMonitor() {

        this.setMonitorState(this.STARTED);


    }

    public void setReceiveState(int receiveState) {

        this.receiveState = receiveState;
    }

    public int getReceiveState() {

        return this.receiveState;
    }


    private int getMonitorState() {

        return this.monitorState;
    }

    private void setMonitorState(int monitorState) {

        this.monitorState = monitorState;
    }

    public int getImagesPerMessage() {

        return this.imagesPerMessage;
    }

    public int getImageCompression() {

        return imageCompression;
    }

    /**
     * Sets the human readable name of the remote peer
     */
    protected void setOriginator(String originator) {

        this.originator = originator;
    }

    /**
     * Gets the human readable name of the remote peer
     */
    public String getOriginator() {

        return this.originator;
    }

    public int getBufferSize() {

        if (incomingBuffer != null) {
            return incomingBuffer.size();
        }

        return 0;
    }


    public long getTimeOfLastMessage() {

        return this.timeOfLastMessage;
    }


    public RemoteMonitorCanvas getMonitorComponent() {

        return this.monitorComponent;

    }

    /**
     *  Accessor for incoming speex buffer size
     */

    /**
     * statistical accessor
     */
    public long getMessagesReceived() {

        return this.messagesReceived;
    }

    /**
     * statistical accessor
     */
    public long getBytesReceived() {

        return this.bytesReceived;
    }

    /**
     * statistical accessor
     */
    public long getAverageImageDecodeTime() {

        return this.averageImageDecodeTime;
    }


    /**
     * statistical accessor
     */
    public long getImageDecodeTime() {

        return this.imageDecodeTime;
    }

    public int getImageSize() {

        return this.imageSize;
    }

    public int averageImageSize() {

        return this.averageImageSize;
    }


    /**
     * statistical accessor
     */
    private void setEncodedMessageSize(int imagesPerMessage) {

        this.imagesPerMessage = imagesPerMessage;
    }

    public Dimension getFormatSize() {

        return this.formatSize != null ? formatSize : new Dimension(0, 0);
    }

    public void setFormatSize(Dimension formatSize) {

        if (formatSize != null) {

            this.formatSize = formatSize;
        } else {

            this.formatSize = new Dimension(0, 0);
        }
    }

    public void setFormatType(String formatType) {

        if (formatType != null) {


            this.formatType = formatType;
        } else {
            this.formatType = this.UNKNOWN_MIME_TYPE;
        }

    }

    public String getFormatType() {

        return this.formatType != null ? this.formatType : this.UNKNOWN_MIME_TYPE;
    }

    public long getImageDrawTime() {

        return this.imageDrawTime;
    }

    public long getAverageImageDrawTime() {

        return this.averageImageDrawTime;
    }


    public boolean isReceive() {

        return this.receive;
    }

    public void setReceive(boolean receive) {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("setReceive " + receive);
        }
        this.receive = receive;
    }


    public void setImageCompression(int imageCompression) {

        this.imageCompression = imageCompression;
    }


    private ViJxtaCallControl getCallControl() {

        return this.viJxtaCallControl;
    }


    class MyLock extends Object {

        private boolean locked = true;

        public boolean isLocked() {

            return this.locked;
        }

        public void setLocked(boolean locked) {

            this.locked = locked;
        }
    }

    class RemoteMonitorCanvas extends JComponent {//Canvas {

        private Dimension size = null;

        private ImageReader reader = null;

        private Image image;

        public RemoteMonitorCanvas(Dimension size) {

            super();

            this.size = size;

            Iterator readers = ImageIO.getImageReadersByFormatName("jpeg");

            reader = (ImageReader) readers.next();

        }

        public Dimension getPreferredSize() {

            if (size == null) {
                size = new Dimension(0, 0);
            }
            return size;
        }

        public void paintComponent(Graphics g) {

            //super.paintComponent (g);

            Graphics2D g2 = (Graphics2D) g;

            g2.drawImage(image, 0, 0, null);

            g2.dispose();

        }

        public void newImage(ByteArrayInputStream encodedImageStream) {
            try {

                long in = System.currentTimeMillis();

                // this is just a impl of ImageInputStreamImpl.. we can subclass
                // later and make our own
                MemoryCacheImageInputStream imageStream = new MemoryCacheImageInputStream(encodedImageStream);

                reader.setInput(imageStream);

                BufferedImage bufferedImage = reader.read(0);

                long out = System.currentTimeMillis();

                imageDecodeTime = out - in;

                in = System.currentTimeMillis();
                // Copy image to buffered image.

                image = bufferedImage;
                //maybe image update????
                //imageUpdate(image,0,0,0, image.getWidth(null),image.getHeight(null));
                // paintComponent(this.getGraphics());
                repaint();
                /*
                Graphics g = this.getGraphics ();
                
                //g.setColor(Color.white);
                //g.fillRect(0, 0, image.getWidth(null),
                //image.getHeight(null));
                g.drawImage (bufferedImage, 0, 0, null);
                g.dispose ();
                 **/
                out = System.currentTimeMillis();

                imageDrawTime = out - in;

            } catch (Exception x) {
                x.printStackTrace();
            }
        }
    }
}
